﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Noris.Tools.FrxEditor.Components;

namespace Noris.Tools.FrxEditor.ComponentsLayer
{
    #region class ViewPort : slouží k přepočtům logických souřadnic na souřadnice vizuální, a k řízení zobrazování daného výřezu dokumentu v zobrazovacím controlu.
    /// <summary>
    /// ViewPort : slouží k přepočtům logických souřadnic na souřadnice vizuální, 
    /// a k řízení zobrazování daného výřezu dokumentu v zobrazovacím controlu.
    /// ViewPort tedy řídí posuny (s účastí Scrollbarů) a změnu Zoomu,
    /// a řídí přepočty souřadnic logických na vizuální a naopak.
    /// </summary>
    public class ViewPort : IViewPortConvertor
    {
        #region Property a jejich proměnné
        /// <summary>
        /// Vytvoří instanci ViewPort, nastaví ID pro hlavní oblast (viz property ViewAreaId).
        /// Hodnota ViewAreaId se může v průběhu života ViewPortu změnit.
        /// </summary>
        /// <param name="viewAreaId">ID oblasti, která reprezentuje vlastní dokument.</param>
        public ViewPort(IComparable viewAreaId)
        {
            this._ViewAreaId = viewAreaId;
            this.OneLineShiftHeight = 5f;
        }
        /// <summary>
        /// ID oblasti, která reprezentuje vlastní dokument.
        /// Vložení hodnoty do ViewAreaId nezpůsobí přepočty ViewPortu.
        /// Vložení souřadnic do oblasti, adresované hodnotou ViewAreaId (pomocí indexeru) způsobí přepočty ViewPortu.
        /// ViewPort může existovat bez určení ViewAreaId.
        /// Pokud je určena hodnota ViewAreaId, pak přístup k souřadnicím přes indexer this[] akceptuje hodnotu ViewAreaId, a při shodě indexu this[ViewAreaId] pracuje s údajem ViewArea.
        /// </summary>
        public IComparable ViewAreaId
        {
            get { return this._ViewAreaId; }
            set { this._ViewAreaId = value; }
        }
        /// <summary>
        /// Režim zoomu
        /// </summary>
        public ViewZoomMode ZoomMode
        {
            get { return this._ZoomMode; }
            set { this._SetZoomMode(value); }
        }
        /// <summary>
        /// <para>
        /// Poměr měřítka, logická hodnota.
        /// Hodnota 1.00 : jeden milimetr logické hodnoty se zobrazuje jako jeden milimetr na displeji.
        /// Hodnota 10.00 : jeden milimetr logické hodnoty se zobrazuje jako deset milimetrů na displeji.
        /// Povolené hodnoty jsou v rozsahu 1/100 až 100. Vložení jiných hodnot je ignorováno.
        /// </para>
        /// <para>
        /// Do fyzických přepočtů (logické milimetry «==» vizuální pixely) ještě zasahuje hodnota ResolutionDPI (počet pixelů na jeden palec).
        /// Vložení hodnoty vede k přepočtu DocArea (s ideálním středem přepočtu v bodě Center),
        /// dále k vyvolání změn na Scrollbarech (volá jejich metodu ViewPortChanged() = s potlačenou zpětnou vazbou)
        /// a následně k vyvolání události Change.
        /// </para>
        /// <para>
        /// Implicitní hodnota je 1.00
        /// </para>
        /// </summary>
        public decimal Zoom
        {
            get { return this._Zoom; }
            set { this._SetZoom(ViewZoomMode.Manual, value); }
        }
        /// <summary>
        /// <para>
        /// Fyzické rozlišení monitoru.
        /// Obsahuje počet pixelů na jeden palec.
        /// Povolené hodnoty jsou v rozsahu 5 až 10000. Vložení jiných hodnot je ignorováno.
        /// </para>
        /// <para>
        /// Ovlivňuje přepočty (logické milimetry «==» vizuální pixely).
        /// Vložení hodnoty vede k přepočtu DocArea (s ideálním středem přepočtu v bodě Center),
        /// dále k vyvolání změn na Scrollbarech (volá jejich metodu ViewPortChanged() = s potlačenou zpětnou vazbou)
        /// a následně k vyvolání události Change.
        /// </para>
        /// <para>
        /// Implicitní hodnota je 100 DPI, vychází se z rozlišení monitoru FullHD=1920×1080 o úhlopříčce 22 palců (šířka obrazu=19.17 palce, rozlišení=(1920/19.17)=100 DPI).
        /// </para>
        /// </summary>
        public decimal ResolutionDPI
        {
            get { return this._ResolutionDPI; }
            set
            {
                if (value < 5m || value > 10000m)
                    return;
                if (value == this._ResolutionDPI)
                    return;
                this._ResolutionDPI = value;
                this._RecalcCurrentVisualRatio();
            }
        }
        /// <summary>
        /// Korekce DPI, typicky = 1.00m. Umožní korigovat pravítko na monitoru podle reálné velikosti atd.
        /// </summary>
        public decimal ResolutionCorrection
        {
            get { return this._ResolutionCorrection; }
            set
            {
                if (value < 0.01m || value > 100)
                    return;
                if (value == this._ResolutionCorrection)
                    return;
                this._ResolutionCorrection = value;
                this._RecalcCurrentVisualRatio();
            }
        }
        /// <summary>
        /// Výška jednoho řádku, o které se posouvá dokument kolečkem myši.
        /// Default = 5mm.
        /// </summary>
        public float OneLineShiftHeight { get; set; }
        /// <summary>
        /// <para>
        /// Aktuální koeficient = počet pixelů na jeden milimetr.
        /// Je výsledkem výpočtu v metodě this._RecalcCurrentVisualRatio().
        /// </para>
        /// <para>
        /// Nemusíme mít obavy o hodnotu, nikdy nebude nula nebo záporná.
        /// </para>
        /// <para>
        /// Následně tedy výpočet vizuálních pixelů = logické milimetry * CurrentVisualRatio.
        /// Výpočet logických milimetrů = vizuální pixely / CurrentVisualRatio.
        /// </para>
        /// </summary>
        public float ZoomRatio
        {
            get { return this._ZoomRatio; }
        }
        /// <summary>
        /// Inicializuje si hodnotu ResolutionDPI z dodaného controlu.
        /// </summary>
        /// <param name="control"></param>
        public void GetResolutionDPIFrom(System.Windows.Forms.Control control)
        {
            using (var graphics = control.CreateGraphics())
            {
                this.ResolutionDPI = (decimal)graphics.DpiX;
            }
        }
        /// <summary>
        /// Vypočte aktuální koeficient CurrentVisualRatio na základě hodnot _Zoom a _ResolutionDPI.
        /// </summary>
        private void _RecalcCurrentVisualRatio()
        {
            this._ZoomRatio = (float)(this._ResolutionDPI / 25.4m * this._Zoom * this._ResolutionCorrection);
        }
        /// <summary>
        /// Souřadnice vizuálního počátku viditelné části controlu, který je zobrazen v bodě 0/0 ViewArea.
        /// Lze číst i setovat, zachovává se velikost ViewArea.
        /// </summary>
        public PointF ViewAreaLocation
        {
            get { return this._ViewArea.Location; }
            set { this.ViewArea = new RectangleF(value, this.ViewAreaSize); }
        }
        /// <summary>
        /// Velikost vizuálního prostoru viditelné části controlu, který je zobrazen prostoru ViewArea.
        /// Lze číst i setovat, zachovává se bod počátku ViewArea.
        /// </summary>
        public SizeF ViewAreaSize
        {
            get { return this._ViewArea.Size; }
            set { this.ViewArea = new RectangleF(this.ViewAreaLocation, value); }
        }
        /// <summary>
        /// Souřadnice vizuálního prostoru, do něhož se promítá obsah dokumentu.
        /// Vizuální prostor zabírá určitou plochu (podmnožinu) controlu, kam se dokument promítá
        /// (další části controlu zabírají Scrollbary, pravítko, záhlaví řádků).
        /// Vložení hodnoty zachovává Zoom, zachovává DocAreaLocation
        /// vede k přepočtu hodnoty DocArea.Size.
        /// </summary>
        public RectangleF ViewArea
        {
            get { return this._ViewArea; }
            set
            {
                this._SetViewArea(value);
            }
        }
        /// <summary>
        /// Souřadnice logického bodu počátku viditelné části dokumentu, který je zobrazen v bodě 0/0 ViewArea.
        /// Lze číst i setovat, zachovává se velikost DocArea.
        /// </summary>
        public PointF DocAreaLocation
        {
            get { return this._DocArea.Location; }
            set { this.DocArea = new RectangleF(value, this.DocAreaSize); }
        }
        /// <summary>
        /// Velikost logického prostoru viditelné části dokumentu, který je zobrazen prostoru ViewArea.
        /// Lze číst i setovat, zachovává se bod počátku DocArea.
        /// </summary>
        public SizeF DocAreaSize
        {
            get { return this._DocArea.Size; }
            set { this.DocArea = new RectangleF(this.DocAreaLocation, value); }
        }
        /// <summary>
        /// Souřadnice logického prostoru dokumentu, který se aktuálně promítá
        /// do viditelné části controlu (do prostoru ViewArea).
        /// Poměr velikosti prostoru DocArea a ViewArea je dán zoomem = hodnotou ZoomRatio.
        /// Vložení hodnoty zachovává souřadnice ViewArea,
        /// ale změní hodnotu ZoomRatio (na základě poměru souřadnic v ose X),
        /// a reálně do DocArea uloží souřadnice přizpůsobené v ose Y novému měřítku.
        /// Důvod: do vizuální plochy (ViewArea) ve tvaru čtverce nelze vložit prostor DocArea
        /// s poměrem stran 2:1, ale přepočte se opět na čtverec a uloží se upravený.
        /// Vložení hodnoty tedy změní hodnotu ZoomRatio,
        /// vede k přepočtu DocArea (s ideálním středem přepočtu v bodě Center),
        /// dále k vyvolání změn na Scrollbarech (volá jejich metodu ViewPortChanged() = s potlačenou zpětnou vazbou)
        /// a následně k vyvolání události Change.
        /// </summary>
        public RectangleF DocArea
        {
            get { return this._DocArea; }
            set
            {
                if (value == this._DocArea)
                    return;
                this._DocArea = value;
            }
        }
        /// <summary>
        /// Celková velikost dokumentu v logických souřadnicích.
        /// </summary>
        public SizeF DocSize
        {
            get { return this._DocSize; }
            set { this._SetDocSize(value); }
        }
        private IComparable _ViewAreaId;
        /// <summary>
        /// Rozlišení monitoru v DPI
        /// </summary>
        private decimal _ResolutionDPI = 100m;          // Implicitní hodnota: pro monitor 22 palců s rozlišením FullHD=1920×1080: šířka obrazu=19.17 palce, rozlišení=(1920/19.17)=100 DPI
        /// <summary>
        /// Režim Zoomu
        /// </summary>
        private ViewZoomMode _ZoomMode = ViewZoomMode.OriginalSize;
        /// <summary>
        /// Měřítko: hodnota 1={1:1}, hodnota 10=10x zvětšené, hodnota 0.5=2x zmenšené atd.
        /// </summary>
        private decimal _Zoom = 1m;
        /// <summary>
        /// Korekce DPI, typicky = 1.00m. Umožní korigovat pravítko na monitoru podle reálné velikosti atd.
        /// </summary>
        private decimal _ResolutionCorrection = 1m;
        /// <summary>
        /// Fyzický koeficient přepočtu: vizuální pixely = logické milimetry * _ZoomRatio.
        /// Hodnotu nastavuje metoda _RecalcCurrentVisualRatio(), volá se po změnách this._ResolutionDPI, this._Zoom a this._ResolutionCorrection
        /// </summary>
        private float _ZoomRatio;
        private RectangleF _ViewArea;
        private RectangleF _DocArea;
        private SizeF _DocSize;
        private bool _NeedEventDraw;
        private bool _EventsSuppressed;
        private bool _NeedEvents { get { return _NeedEventDraw; } }
        #endregion
        #region Public metody
        /// <summary>
        /// Metoda zajistí Reset ViewPortu.
        /// Typicky se volá po načtení dokumentu, v situaci kdy jsou rozměry ViewPortu již známé (nemusí být známa velikost dokumentu).
        /// To obnáší: nastavení zoomu na 1.00, nastavení pozice DocArea na počátek v bodě 0/0 a jeho velikost odpovídající aktuálnímu ViewArea.
        /// Nemění se DocSize (velková velikost dokumentu). Nevolají se eventy.
        /// </summary>
        public void ResetView()
        {
            this.Zoom = 1.0m;
            this.DocAreaLocation = new PointF(0f, 0f);
            this.DocAreaSize = this._LogicalFromVisualS(this.ViewAreaSize);
        }
        /// <summary>
        /// Metoda zajistí zrušení případných čekajících eventů.
        /// Typicky se volá po vykreslení obsahu ViewPortu.
        /// </summary>
        public void ResetEvents()
        {
            this._ClearEvents();
        }
        #endregion
        #region Řešení posunů a Zoomu pomocí kolečka myši
        /// <summary>
        /// Zajistí posun dokumentu při otočení kolečka myši
        /// </summary>
        /// <param name="visualPoint"></param>
        /// <param name="delta"></param>
        /// <returns></returns>
        public bool WheelShiftY(Point visualPoint, int delta)
        {
            // Posun se provádí v logických souřadnicích.
            // Jedno pootočení myši = počet řádků MouseWheelScrollLines, jeden řádek v reportu = 0.85 cm:
            int lineCnt = System.Windows.Forms.SystemInformation.MouseWheelScrollLines;
            float distY = (float)lineCnt * this.OneLineShiftHeight * (delta < 0 ? 1f : -1f);
            RectangleF docArea = this.DocArea;
            docArea.Y += distY;
            docArea = this._AlignDocArea(docArea);
            bool isChange = (docArea.Y != this.DocArea.Y);
            if (isChange)
                this.DocArea = docArea;
            return isChange;
        }
        /// <summary>
        /// Změní Zoom, typicky po otočení kolečka myši spolus klávesou Ctrl.
        /// Zoom se mění nad bodem, kde se myš nachází.
        /// Rovněž je možno volat tuto metodu, pokud je aktivní režim Zoom a kliká se někde myší, kde je třeba změnit zoom.
        /// </summary>
        /// <param name="visualPoint"></param>
        /// <param name="delta"></param>
        /// <returns></returns>
        public bool WheelZoom(Point visualPoint, int delta)
        {
            PointD pointRel = this._ViewArea.GetPointDRelative(visualPoint);   // Relativní pozice myši vzhledem k ViewArea
            PointF pointLog = this._DocArea.GetPointFAbsolute(pointRel);       // Pozice bodu odpovídající myši, v logických absolutních dimenzích
            
            decimal zoom = this._Zoom;
            if (delta < 0)
                zoom = zoom / this.ZoomStepRatio;
            else if (delta > 0)
                zoom = zoom * this.ZoomStepRatio;

            this._SetZoom(ViewZoomMode.Manual, zoom, pointLog);
            return true;
        }
        #endregion
        #region Zoom Mode
        /// <summary>
        /// Nastaví požadovaný režim zoomu
        /// </summary>
        /// <param name="value"></param>
        private void _SetZoomMode(ViewZoomMode value)
        {
            switch (value)
            {
                case ViewZoomMode.WholeWidth:
                    this._SetZoomWholeWidth();
                    break;
                case ViewZoomMode.WholePage:
                    this._SetZoomWholePage();
                    break;
                case ViewZoomMode.Set10pc:
                    this._SetZoom(value, 0.10m);
                    break;
                case ViewZoomMode.Set25pc:
                    this._SetZoom(value, 0.25m);
                    break;
                case ViewZoomMode.Set50pc:
                    this._SetZoom(value, 0.50m);
                    break;
                case ViewZoomMode.Set75pc:
                    this._SetZoom(value, 0.75m);
                    break;
                case ViewZoomMode.OriginalSize:
                    this._SetZoom(value, 1.00m);
                    break;
                case ViewZoomMode.Set150pc:
                    this._SetZoom(value, 1.5m);
                    break;
                case ViewZoomMode.Set250pc:
                    this._SetZoom(value, 2.5m);
                    break;
                case ViewZoomMode.Set500pc:
                    this._SetZoom(value, 5.0m);
                    break;
                case ViewZoomMode.ZoomInStep:
                    this._SetZoom(ViewZoomMode.Manual, this._Zoom * this.ZoomStepRatio);
                    break;
                case ViewZoomMode.ZoomOutStep:
                    this._SetZoom(ViewZoomMode.Manual, this._Zoom / this.ZoomStepRatio);
                    break;
            }
        }
        /// <summary>
        /// Nastaví takový Zoom, aby byla vidět celá stránka - na šířku
        /// </summary>
        private void _SetZoomWholeWidth()
        {
            decimal zoom = this._GetZoom(this.ViewArea.Width - this.VisualMargin, this.DocSize.Width);
            this._SetZoom(ViewZoomMode.WholeWidth, zoom);
        }
        /// <summary>
        /// Nastaví takový Zoom, aby byla vidět celá stránka jak na šířku, tak na výšku
        /// </summary>
        private void _SetZoomWholePage()
        {
            decimal zoomH = this._GetZoom(this.ViewArea.Width - this.VisualMargin, this.DocSize.Width);
            decimal zoomV = this._GetZoom(this.ViewArea.Height - this.VisualMargin, this.DocSize.Height);
            decimal zoom = Math.Min(zoomH, zoomV);
            this._SetZoom(ViewZoomMode.WholeWidth, zoom);
        }
        /// <summary>
        /// Nastaví hodnoty Zoomu, přepočte koeficienty
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="zoom"></param>
        private void _SetZoom(ViewZoomMode mode, decimal zoom)
        {
            if (zoom < 0.01m || zoom > 100m) return;
            if (zoom == this._Zoom) return;

            this._ZoomMode = mode;
            this._Zoom = zoom;
            this._RecalcCurrentVisualRatio();

            // Změna Zoomu provede změnu prostoru DocArea tak, aby zobrazená velikost odpovídala aktuální velikosti ViewArea a novému měřítku:
            SizeF newSize = this._LogicalFromVisualS(this._ViewArea.Size);
            RectangleF newArea = new RectangleF(this._DocArea.Location, newSize);
            newArea = this._AlignDocArea(newArea);                                   // Korekce pozice viditelné oblasti dokumentu
            if (newArea != this._DocArea)
            {
                this._DocArea = newArea;
            }
        }
        /// <summary>
        /// Nastaví hodnoty Zoomu při určení pevného bodu, přepočte koeficienty.
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="zoom"></param>
        /// <param name="fixedPoint">Pevný bod</param>
        private void _SetZoom(ViewZoomMode mode, decimal zoom, PointF fixedPoint)
        {
            if (zoom < 0.01m || zoom > 100m) return;
            if (zoom == this._Zoom) return;

            this._ZoomMode = mode;
            this._Zoom = zoom;
            this._RecalcCurrentVisualRatio();

            PointD relativePivotPoint = this._DocArea.GetPointDRelative(fixedPoint);  // Relativní souřadnice pevného bodu
            SizeF newSize = this._LogicalFromVisualS(this._ViewArea.Size);
            RectangleF newArea = fixedPoint.GetRectangleFFromRelativePoint(newSize, relativePivotPoint);
            newArea = this._AlignDocArea(newArea);                                    // Korekce pozice viditelné oblasti dokumentu
            if (newArea != this._DocArea)
            {
                this._DocArea = newArea;
            }
        }
        /// <summary>
        /// Vypočte hodnotu Zoom na základě dané vizuální dimenze v pixelech a jí odpovídající logické dimenze v milimetrech.
        /// Akceptuje přitom další koeficienty (_ResolutionDPI, _ResolutionCorrection).
        /// </summary>
        /// <param name="visualDim"></param>
        /// <param name="logicalDim"></param>
        /// <returns></returns>
        private decimal _GetZoom(float visualDim, float logicalDim)
        {
            decimal zoomRatio = (decimal)visualDim / (decimal)logicalDim;
            decimal zoom = zoomRatio / (this._ResolutionDPI / 25.4m * this._ResolutionCorrection);
            return zoom;
        }
        /// <summary>
        /// Koeficient změny zoom na toolbaru +/- zoom, a na kolečku myši.
        /// Výchozí hodnota = 1.10 = změna 10%
        /// </summary>
        protected virtual decimal ZoomStepRatio { get { return 1.1m; } }
        #endregion
        #region Podpora pro ScrollBary
        /// <summary>
        /// Obsahuje (vytvoří) data pro horizontální (vodorovný) ScrollBar.
        /// Data popisují rozsah dokumentu (velikost), aktuální pozici i poměrnou velikost viditelné části dokumentu z celku.
        /// </summary>
        public ScrollBarData ScrollBarDataH
        {
            get { return new ScrollBarData() { Minimum = 0f, Maximum = this.DocSize.Width + this._LogicalFromVisualD(5f), CurrentBegin = this.DocArea.X, Showing = this.DocArea.Width, SuppressEvents = true }; }
        }
        /// <summary>
        /// Obsahuje (vytvoří) data pro vertikální (svislý) ScrollBar.
        /// Data popisují rozsah dokumentu (velikost), aktuální pozici i poměrnou velikost viditelné části dokumentu z celku.
        /// </summary>
        public ScrollBarData ScrollBarDataV
        {
            get { return new ScrollBarData() { Minimum = 0f, Maximum = this.DocSize.Height + this._LogicalFromVisualD(5f), CurrentBegin = this.DocArea.Y, Showing = this.DocArea.Height, SuppressEvents = true }; }
        }
        #endregion
        #region Change events: registrování potřebných eventů, hromadné vyvolání eventů.
        /// <summary>
        /// Vyvolá eventy o změně, pokud jsou povoleny (není EventsSuppressed) a jsou registrovány (NeedEvent???).
        /// Vyvoláním eventu dojde ke shození jejich příznaku NeedEvent???.
        /// Potlačené eventy příznak NeedEvent??? neshodí.
        /// </summary>
        private void _CallEvents()
        {
            if (this._EventsSuppressed) return;         // Blokované eventy

            if (this._NeedEventDraw)
            {
                this._NeedEventDraw = false;

            }
        }
        /// <summary>
        /// Zruší příznaky potřebných eventů (NeedEvent???).
        /// Provede se bez ohledu na blokování eventů (EventsSuppressed).
        /// </summary>
        private void _ClearEvents()
        {
            this._NeedEventDraw = false;
        }
        #endregion
        #region Suppress & Resume events
        /// <summary>
        /// Potlačí volání eventů o změnách.
        /// Je nutno volat v using patternu. na konci usingu se změny zase povolí, jinak budou potlačeny stále.
        /// Tato třída (ViewPort) nemá metodu ResumeEvents(), namísto ní funguje konec using bloku.
        /// Parametr SupressEvents(executeChangeOnEnd) ovlivní, zda na konci bloku proběhnou potlačené eventy (true) nebo zda budou úplně zrušeny (false).
        /// </summary>
        /// <param name="executeChangeOnEnd">true: na konci using bloku vykonat najednou všechny potlačené eventy / false: neprovádět vůbec.</param>
        public IDisposable SuppressEvents(bool executeChangeOnEnd)
        {
            this._EventsSuppressed = true;
            this._ClearEvents();
            return new DisposeActionCaller<bool>(this._ResumeEvents, executeChangeOnEnd);  // Instance třídy DisposeActionCaller zajistí, že při jejím Dispose se zavolá daná metoda s danými daty.
        }
        /// <summary>
        /// Obnoví volání eventů o změnách. Každá následná změna dat už standardně vyvolá event Change.
        /// Vždy vyvolá metodu ViewPortChanged() na obou Scrollbarech.
        /// Pokud bude předán parametr callChange == true, pak vyvolá i event Change a dojde k překreslení Controlu.
        /// </summary>
        private void _ResumeEvents(bool executeChangeOnEnd)
        {
            this._EventsSuppressed = false;
            if (executeChangeOnEnd && this._NeedEvents)
                this._CallEvents();
            this._ClearEvents();
        }
        /// <summary>
        /// Příznak, který při hodnotě true potlačuje volání všech eventů při změnách.
        /// Nevolají se ani změny Scrollbarů, ani event Change.
        /// Nelze setovat, k řízení se používají metody SuspendEventChange() a ResumeEventChange().
        /// </summary>
        public bool EventsSuppressed { get { return this._EventsSuppressed; } }
        #endregion
        #region Provádění přepočtů
        /// <summary>
        /// Vloží nové vizuální souřadnice ViewPortu.
        /// Ponechává v platnosti Zoom i počátek DocArea (jeho Location),
        /// upraví velikost DocArea.Size.
        /// Používá se typicky po Resize controlu, který představuje Editor;
        /// anebo po přesunutí interních prvků editoru (šířka Bands, výška pravítka, atd).
        /// </summary>
        /// <param name="viewArea"></param>
        private void _SetViewArea(RectangleF viewArea)
        {
            if (viewArea.Width <= 0f || viewArea.Height <= 0f) return;               // Prázdné hodnoty neberu

            if (viewArea == this._ViewArea) return;
            this._ViewArea = viewArea;
            this._NeedEventDraw = true;

            SizeF logDocSize = this._LogicalFromVisualS(viewArea.Size);              // Viditelná velikost dokumentu v logických jednotkách, je daná vizuální plochou a měřítkem
            RectangleF docArea = new RectangleF(this._DocArea.Location, logDocSize); // Viditelná oblast dokumentu, bez korekcí
            docArea = this._AlignDocArea(docArea);                                   // Korekce pozice viditelné oblasti dokumentu
            if (docArea == this._DocArea) return;
            this._DocArea = docArea;
        }
        /// <summary>
        /// Vložení nové velikosti dokumentu, v logických jednotkách.
        /// Může mít vliv na zobrazení (pokud by dokument byl menší než dříve, a pokud by byl zobrazen jeho konec, pak dojde k posunu zobrazené části dokumentu směrem k začátku).
        /// </summary>
        /// <param name="docSize"></param>
        private void _SetDocSize(SizeF docSize)
        {
            if (docSize == this._DocSize) return;
            this._DocSize = docSize;
            this._NeedEventDraw = true;

            RectangleF docArea = this._AlignDocArea(this._DocArea);                  // Aktuální viditelná oblast dokumentu, po provedení korekcí (zmenšení velikosti dokumentu v případě potřeby vede...
            if (docArea == this._DocArea) return;                                    //  ... k posunu počátku viditelné oblasti dokumentu, ale ne k velikosti DocArea)
            this._DocArea = docArea;                                                 // Nastavení provedu "bez aplikační logiky", ta je provedena právě zde.
        }
        /// <summary>
        /// Vložené nové viditelné oblasti dokumentu, v logických jednotkách.
        /// </summary>
        /// <param name="docArea"></param>
        private void _SetDocArea(RectangleF docArea)
        {
            if (docArea.Width <= 0f || docArea.Height <= 0f) return;                 // Prázdné hodnoty neberu

            RectangleF newArea = this._AlignDocArea(docArea);                        // Korekce pozice viditelné oblasti dokumentu
            if (newArea == this._DocArea) return;
            RectangleF oldArea = this._DocArea;

            if (oldArea.Size != newArea.Size)
            {   // Došlo ke změně velikosti viditelné části dokumentu (a přitom vizuální prostor se nezměnil) => musíme přepočítat Zoom, 
                //  ale bez logiky obsažené u změny Zoomu (ta by přepočítala DocArea, a tu právě zde měníme explicitně).
                // Zoom přepočteme ze souřadnice v ose X, a podle nového Zoomu a ViewArea určíme korektně velikost DocArea.Size (udržujeme poměr stran X/Y podle ViewAreaSize).
                this._ZoomMode = ViewZoomMode.Manual;
                this._Zoom = this._GetZoom(this._ViewArea.Width, newArea.Width);
                this._RecalcCurrentVisualRatio();
                newArea.Size = this._LogicalFromVisualS(this._ViewArea.Size);
            }

            this._DocArea = docArea;
            this._NeedEventDraw = true;

        }
        /// <summary>
        /// Zajistí, že dané souřadnice prostoru (budoucí DocArea) budou korektní.
        /// Korektní: X a Y nebudou záporné, a prostor nebude zbytečně posunutý doleva / nahoru.
        /// Pokud by vedle prostoru DocArea bylo vpravo a/nebo dole více místa, než 10 pixelů, pak se prostor posune doprava/dolů.
        /// Přitom se ale "neodlepí" od levého/horního okraje (jeho X a Y nebudou záporné).
        /// </summary>
        /// <param name="docArea"></param>
        /// <returns></returns>
        private RectangleF _AlignDocArea(RectangleF docArea)
        {
            float logicalSpace = _LogicalFromVisualD(this.VisualMargin);   // Následné kontroly zajistí, že dokument neuteče doleva a nahoru, optimálně vedle něj bude 10 pixelů Workspace. Přepočet z 10px na logickou dimenzi.
            SizeF docSize = this._DocSize;
            RectangleF result = docArea;

            if (result.Right > (docSize.Width + logicalSpace))
                result.X = docSize.Width + logicalSpace - result.Width;
            if (result.X < 0f)
                result.X = 0f;

            if (result.Bottom > (docSize.Height + logicalSpace))
                result.Y = docSize.Height + logicalSpace - result.Height;
            if (result.Y < 0f)
                result.Y = 0f;

            return result;
        }
        /// <summary>
        /// Prostor v pixelech, který optimálně udržujeme volný vpravo a dole pod dokumentem, když chceme zobrazit okraj dokumentu.
        /// Default = 10 pixelů.
        /// </summary>
        protected virtual float VisualMargin { get { return 10f; } }
        #endregion
        #region Úložiště souřadnic: this[IComparable areaId]
        /// <summary>
        /// ViewPort obsahuje úložiště souřadnic různých prostorů.
        /// Uživatel ViewPortu do něj vkládá (a může číst) souřadnice, 
        /// tyto souřadnice ViewPort následně zpřístupňuje přes interface IViewPortConvertor metodou GetVisualArea().
        /// Zápis souřadnic (set) vyhodí chybu NullReference, pokud je zadáno areaId = null.
        /// Čtení souřadnic (get) stejně tak, a navíc vyhodí chybu 
        /// </summary>
        /// <param name="areaId"></param>
        /// <returns></returns>
        public RectangleF this[IComparable areaId]
        {
            get
            {
                this._AreaIdCheck(areaId);
                if (this.ViewAreaId != null && this.ViewAreaId.CompareTo(areaId) == 0)
                    return this.ViewArea;
                this._AreaDictCheck();
                RectangleF result;
                if (!this._AreaDict.TryGetValue(areaId, out result))
                    throw new System.Collections.Generic.KeyNotFoundException("ViewPort[AreaID]: AreaID »" + areaId.ToString() + "« does not exists in ViewPort.");
                return result;
            }
            set
            {
                this._AreaIdCheck(areaId);
                if (this.ViewAreaId != null && this.ViewAreaId.CompareTo(areaId) == 0)
                    this.ViewArea = value;
                else
                {
                    this._AreaDictCheck();
                    if (this._AreaDict.ContainsKey(areaId))
                        this._AreaDict[areaId] = value;
                    else
                        this._AreaDict.Add(areaId, value);
                }
            }
        }
        /// <summary>
        /// Vrátí informaci o tom, zda tento ViewPort obsahuje souřadnice daného klíče (areaId).
        /// Souřadnice se vkládají pomocí indexeru ViewPortu.
        /// </summary>
        /// <param name="areaId"></param>
        /// <returns></returns>
        public bool ContainArea(IComparable areaId)
        {
            _AreaIdCheck(areaId);
            _AreaDictCheck();
            return this._AreaDict.ContainsKey(areaId);
        }
        /// <summary>
        /// Odstraní všechny souřadnice z ViewPortu.
        /// </summary>
        public void ClearArea()
        {
            this._AreaDict = null;
        }
        private void _AreaIdCheck(IComparable areaId)
        {
            if (areaId == null)
                throw new ArgumentNullException("ViewPort[AreaID]: AreaID is null.");
        }
        private void _AreaDictCheck()
        {
            if (this._AreaDict == null)
                this._AreaDict = new Dictionary<IComparable, RectangleF>();
        }
        private Dictionary<IComparable, RectangleF> _AreaDict;
        #endregion
        #region Fyzické přepočty
        #region 1) Přepočty na vizuální data z logických hodnot
        /// <summary>
        /// Vrátí vizuální vzdálenost v pixelech, pro zadanou logickou vzdálenost v milimetrech.
        /// </summary>
        /// <param name="logicalDistance"></param>
        /// <returns></returns>
        private float _VisualFromLogicalD(float logicalDistance)
        {
            return logicalDistance * this.ZoomRatio;
        }
        /// <summary>
        /// Vrátí vizuální souřadnice bodu v pixelech, pro zadaný logický bod uvedený v milimetrech.
        /// Vstupní logická souřadnice je uvedena v koordinátech dokumentu.
        /// Výsledná vizuální souřadnice je uvedena v koordinátech controlu.
        /// </summary>
        /// <param name="logicalPoint"></param>
        /// <returns></returns>
        private PointF _VisualFromLogicalP(PointF logicalPoint)
        {
            return new PointF(this._VisualFromLogicalX(logicalPoint.X), this._VisualFromLogicalY(logicalPoint.Y));
        }
        /// <summary>
        /// Vrátí vizuální velikost v pixelech, pro zadanou velikost v milimetrech.
        /// </summary>
        /// <param name="logicalSize"></param>
        /// <returns></returns>
        private SizeF _VisualFromLogicalS(SizeF logicalSize)
        {
            return new SizeF(this._VisualFromLogicalD(logicalSize.Width), this._VisualFromLogicalD(logicalSize.Height));
        }
        /// <summary>
        /// Vrátí vizuální souřadnice prostoru v pixelech, pro zadaný logický prostor uvedený v milimetrech.
        /// Vstupní logická souřadnice je uvedena v koordinátech dokumentu.
        /// Výsledná vizuální souřadnice je uvedena v koordinátech controlu.
        /// </summary>
        /// <param name="logicalArea"></param>
        /// <returns></returns>
        private RectangleF _VisualFromLogicalR(RectangleF logicalArea)
        {
            return new RectangleF(this._VisualFromLogicalP(logicalArea.Location), this._VisualFromLogicalS(logicalArea.Size));
        }
        /// <summary>
        /// Vrátí vizuální souřadnici X v pixelech, pro zadanou logickou souřadnici X v milimetrech.
        /// Vstupní logická souřadnice je uvedena v koordinátech dokumentu.
        /// Výsledná vizuální souřadnice je uvedena v koordinátech controlu.
        /// </summary>
        /// <param name="logicalDistance"></param>
        /// <returns></returns>
        private float _VisualFromLogicalX(float logicalDistance)
        {
            return ((logicalDistance - this.DocArea.X) * this.ZoomRatio) + this.ViewArea.X;
        }
        /// <summary>
        /// Vrátí vizuální souřadnici Y v pixelech, pro zadanou logickou souřadnici Y v milimetrech.
        /// Vstupní logická souřadnice je uvedena v koordinátech dokumentu.
        /// Výsledná vizuální souřadnice je uvedena v koordinátech controlu.
        /// </summary>
        /// <param name="logicalDistance"></param>
        /// <returns></returns>
        private float _VisualFromLogicalY(float logicalDistance)
        {
            return ((logicalDistance - this.DocArea.Y) * this.ZoomRatio) + this.ViewArea.Y;
        }
        #endregion
        #region 2) Přepočty na logická data z vizuálních hodnot
        /// <summary>
        /// Vrátí logickou vzdálenost v milimetrech, pro zadanou vizuální vzdálenost v pixelech.
        /// </summary>
        /// <param name="visualDistance"></param>
        /// <returns></returns>
        private float _LogicalFromVisualD(float visualDistance)
        {
            return visualDistance / this.ZoomRatio;
        }
        /// <summary>
        /// Vrátí logické souřadnice bodu v milimetrech, pro zadaný vizuální bod uvedený v pixelech.
        /// Vstupní vizuální souřadnice je uvedena v koordinátech controlu.
        /// Výsledná logická souřadnice je uvedena v koordinátech dokumentu.
        /// </summary>
        /// <param name="visualPoint"></param>
        /// <returns></returns>
        private PointF _LogicalFromVisualP(PointF visualPoint)
        {
            return new PointF(this._LogicalFromVisualX(visualPoint.X), this._LogicalFromVisualY(visualPoint.Y));
        }
        /// <summary>
        /// Vrátí logickou velikost v milimetrech, pro zadanou vizuální velikost v pixelech.
        /// </summary>
        /// <param name="visualSize"></param>
        /// <returns></returns>
        private SizeF _LogicalFromVisualS(SizeF visualSize)
        {
            return new SizeF(this._LogicalFromVisualD(visualSize.Width), this._LogicalFromVisualD(visualSize.Height));
        }
        /// <summary>
        /// Vrátí logické souřadnice prostoru v milimetrech, pro zadaný vizuální prostor uvedený v pixelech.
        /// Vstupní vizuální souřadnice je uvedena v koordinátech controlu.
        /// Výsledná logická souřadnice je uvedena v koordinátech dokumentu.
        /// </summary>
        /// <param name="visualArea"></param>
        /// <returns></returns>
        private RectangleF _LogicalFromVisualR(RectangleF visualArea)
        {
            return new RectangleF(this._LogicalFromVisualP(visualArea.Location), this._LogicalFromVisualS(visualArea.Size));
        }
        /// <summary>
        /// Vrátí logickou souřadnici X v milimetrech, pro zadanou vizuální souřadnici X v pixelech.
        /// Vstupní vizuální souřadnici je uvedena v koordinátech controlu.
        /// Výsledná logická souřadnice je uvedena v koordinátech dokumentu.
        /// </summary>
        /// <param name="visualDistance"></param>
        /// <returns></returns>
        private float _LogicalFromVisualX(float visualDistance)
        {
            return ((visualDistance - this.ViewArea.X) / this.ZoomRatio) + this.DocArea.X;
        }
        /// <summary>
        /// Vrátí logickou souřadnici Y v milimetrech, pro zadanou vizuální souřadnici Y v pixelech.
        /// Vstupní vizuální souřadnici je uvedena v koordinátech controlu.
        /// Výsledná logická souřadnice je uvedena v koordinátech dokumentu.
        /// </summary>
        /// <param name="visualDistance"></param>
        /// <returns></returns>
        private float _LogicalFromVisualY(float visualDistance)
        {
            return ((visualDistance - this.ViewArea.Y) / this.ZoomRatio) + this.DocArea.Y;
        }
        #endregion
        #endregion
        #region IViewPortConvertor Members
        decimal IViewPortConvertor.Zoom { get { return this.Zoom; } }
        RectangleF IViewPortConvertor.ViewArea { get { return this.ViewArea; } }
        float IViewPortConvertor.VisualFromLogical(float logicalDistance) { return this._VisualFromLogicalD(logicalDistance); }
        PointF IViewPortConvertor.VisualFromLogical(PointF logicalPoint) { return this._VisualFromLogicalP(logicalPoint); }
        SizeF IViewPortConvertor.VisualFromLogical(SizeF logicalSize) { return this._VisualFromLogicalS(logicalSize); }
        RectangleF IViewPortConvertor.VisualFromLogical(RectangleF logicalArea) { return this._VisualFromLogicalR(logicalArea); }
        float IViewPortConvertor.LogicalFromVisual(float visualDistance) { return this._LogicalFromVisualD(visualDistance); }
        PointF IViewPortConvertor.LogicalFromVisual(PointF visualPoint) { return this._LogicalFromVisualP(visualPoint); }
        SizeF IViewPortConvertor.LogicalFromVisual(SizeF visualSize) { return this._LogicalFromVisualS(visualSize); }
        RectangleF IViewPortConvertor.LogicalFromVisual(RectangleF visualArea) { return this._LogicalFromVisualR(visualArea); }
        RectangleF IViewPortConvertor.GetVisualArea(IComparable areaId) { return this[areaId]; }
        #endregion
    }
    #endregion
    #region enum ViewZoomMode : Režim zoomu
    /// <summary>
    /// Režim zoomu
    /// </summary>
    public enum ViewZoomMode
    {
        /// <summary>Nezadáno</summary>
        None = 0,
        /// <summary>Ruční zoom, libovolná hodnota</summary>
        Manual,
        /// <summary>Původní velikost (1:1)</summary>
        OriginalSize,
        /// <summary>Zobrazit celou šířku</summary>
        WholeWidth,
        /// <summary>Zobrazit celou stránku</summary>
        WholePage,
        /// <summary>Měřítko 10% (zmenšené 1:10)</summary>
        Set10pc,
        /// <summary>Měřítko 25% (zmenšené 1:4)</summary>
        Set25pc,
        /// <summary>Měřítko 50% (zmenšené 1:2)</summary>
        Set50pc,
        /// <summary>Měřítko 75% (zmenšené 3:4)</summary>
        Set75pc,
        /// <summary>Měřítko 150% (zvětšené 3:2)</summary>
        Set150pc,
        /// <summary>Měřítko 250% (zvětšené 5:2)</summary>
        Set250pc,
        /// <summary>Měřítko 500% (zvětšené 5:1)</summary>
        Set500pc,
        /// <summary>Zvětšit měřítko o 10%</summary>
        ZoomInStep,
        /// <summary>Zmenšit měřítko o 10%</summary>
        ZoomOutStep
    }
    #endregion
    #region interface IViewPortConvertor : předpis metod pro obecné fungování ViewPortu
    /// <summary>
    /// Interface, který předepisuje přítomnost metod pro konverzi logických souřadnic na vizuální, a naopak.
    /// </summary>
    public interface IViewPortConvertor
    {
        /// <summary>
        /// Aktuální zoom, uvádí poměr zvětšení z vizuálních na logické prvky (například hodnota 2 reprezentuje 2-násobné zvětšení)
        /// </summary>
        decimal Zoom { get; }
        /// <summary>
        /// Souřadnice vizuálního prostoru ViewPortu v rámci controlu, v pixelech
        /// </summary>
        RectangleF ViewArea { get; }
        /// <summary>
        /// Vrátí vizuální rozměr (vzdálenost), odpovídající rozměru logickému
        /// </summary>
        /// <param name="logicalDistance"></param>
        /// <returns></returns>
        float VisualFromLogical(float logicalDistance);
        /// <summary>
        /// Vrátí vizuální souřadnice bodu, odpovídající souřadnici logického bodu
        /// </summary>
        /// <param name="logicalPoint"></param>
        /// <returns></returns>
        PointF VisualFromLogical(PointF logicalPoint);
        /// <summary>
        /// Vrátí vizuální velikost plochy, odpovídající velikosti logické plochy
        /// </summary>
        /// <param name="logicalSize"></param>
        /// <returns></returns>
        SizeF VisualFromLogical(SizeF logicalSize);
        /// <summary>
        /// Vrátí vizuální souřadnice prostoru, odpovídající souřadnicím logického prostoru
        /// </summary>
        /// <param name="logicalArea"></param>
        /// <returns></returns>
        RectangleF VisualFromLogical(RectangleF logicalArea);
        /// <summary>
        /// Vrátí logický rozměr (vzdálenost), odpovídající rozměru vizuálnímu
        /// </summary>
        /// <param name="visualDistance"></param>
        /// <returns></returns>
        float LogicalFromVisual(float visualDistance);
        /// <summary>
        /// Vrátí logické souřadnice bodu, odpovídající souřadnici vizuálního bodu
        /// </summary>
        /// <param name="visualPoint"></param>
        /// <returns></returns>
        PointF LogicalFromVisual(PointF visualPoint);
        /// <summary>
        /// Vrátí logickou velikost plochy, odpovídající velikosti vizuální plochy
        /// </summary>
        /// <param name="visualSize"></param>
        /// <returns></returns>
        SizeF LogicalFromVisual(SizeF visualSize);
        /// <summary>
        /// Vrátí logické souřadnice prostoru, odpovídající souřadnicím vizuálního prostoru
        /// </summary>
        /// <param name="visualArea"></param>
        /// <returns></returns>
        RectangleF LogicalFromVisual(RectangleF visualArea);
        /// <summary>
        /// Vrátí vizuální souřadnice pro požadovaný prvek.
        /// ViewPort může obsahovat různé prostory, typicky: Dokument, Pravítko vlevo a nahoře, Scrollbar vpravo a dole, atd.
        /// ViewPort (IViewPortConvertor) jako svoji službu tedy dokáže vrátit dané vizuální souřadnice.
        /// Pokud ale budou žádány souřadnice neznámého druhu (které neexistují v paměti ViewPortu), musí vyhodit chybu.
        /// </summary>
        /// <param name="areaId"></param>
        /// <returns></returns>
        RectangleF GetVisualArea(IComparable areaId);
    }
    #endregion
}
